package alibaba

import (
	"context"
	"errors"
	"fmt"
	"os/exec"
	"strconv"
	"strings"

	"golang.aliyun-inc.com/agit/agit/proc-receive-hook/protocol"
)

type DivergeChecker interface {
	checkDiverge(ctx context.Context, command *protocol.Command, params *AGitFlowParams, mr *MergeRequestResp) error
	checkFastforward(command *protocol.Command, mrOldSource string) (bool, error)
	checkCR(ctx context.Context, command *protocol.Command, cr *CodeReviewBasicInfo, patch *CodeReviewPatchSet, params *AGitFlowParams, oldCommit string) error
}

type CodeupDivergeChecker struct {
}

func (divergeChecker *CodeupDivergeChecker) checkDiverge(ctx context.Context, command *protocol.Command, params *AGitFlowParams, mr *MergeRequestResp) error {
	//不允许修改target
	mrOldTarget := mr.TargetBranch
	thisTarget := params.TargetBranch
	if thisTarget != "" && thisTarget != mrOldTarget {
		return fmt.Errorf("MR #%d points to %s, but you pushed to another branch: %s", mr.ID, mrOldTarget, thisTarget)
	}

	shallFail := true
	mrOldSource := mr.SourceBranch
	switch params.OldCommit {
	//未传old-oid, 校验是否同一用户推送，或者fast forward提交
	case "":
		sameuser := params.UserID == strconv.Itoa(mr.UpdatedByID)
		fastforward, err := divergeChecker.checkFastforward(command, mrOldSource)
		if err != nil {
			return err
		}
		shallFail = !(sameuser || fastforward)
	//传了old-oid, 校验是否匹配
	default:
		shallFail = !strings.HasPrefix(mrOldSource, params.OldCommit)
	}
	if shallFail {
		return fmt.Errorf("MR is diverged with this push")
	}
	return nil
}

func (divergeChecker *CodeupDivergeChecker) checkFastforward(command *protocol.Command, mrOldSource string) (bool, error) {
	cmdOldOid := command.OldOID
	if cmdOldOid == NullSHA1 {
		return true, nil
	}
	gitCmd := fmt.Sprintf("git rev-list %s..%s --reverse", command.OldOID, command.NewOID)
	out, err := exec.Command("bash", "-c", gitCmd).Output()
	if err != nil {
		return false, fmt.Errorf("failed to get commits between %s and %s", cmdOldOid, command.NewOID)
	}
	commits := strings.Split(string(out), "\n")
	diverge := cmdOldOid != mrOldSource
	fastForward := false
	if diverge {
		for _, commit := range commits {
			if commit == mrOldSource {
				fastForward = true
				break
			}
		}
	}
	return !diverge || fastForward, nil
}

func (divergeChecker *CodeupDivergeChecker) checkCR(ctx context.Context, command *protocol.Command, cr *CodeReviewBasicInfo, patch *CodeReviewPatchSet, params *AGitFlowParams, oldCommit string) (err error) {
	isAuthor := params.UserID == strconv.Itoa(cr.Author.ID)
	isReviewer := false
	for _, assignee := range cr.Reviewers {
		if params.UserID == strconv.Itoa(assignee.ID) {
			isReviewer = true
			break
		}
	}
	if !isAuthor && !isReviewer {
		return errors.New("you need to be the author or reviewer to update the code review")
	}

	if cr.CreateFrom != ClientCreateMethod {
		return errors.New("can not update web created code review by command line")
	}

	if cr.Status == "MERGED" || cr.Status == "CLOSED" {
		return errors.New("can not update closed or merged code review")
	}

	if oldCommit != "" {
		if !strings.HasPrefix(cr.SourceCommitID, oldCommit) {
			return errors.New("old-oid do not match, update code review failed")
		}
		return nil
	}

	sameuser := params.UserID == fmt.Sprintf("%d", patch.UserID)
	if sameuser {
		return nil
	}
	fastforward, err := divergeChecker.checkFastforward(command, cr.SourceCommitID)
	if err != nil {
		return
	}

	if !sameuser && !fastforward {
		return errors.New("code review is diverged with this push")
	}
	return nil
}
